/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.virtual.model.startup;

import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.version.Version;
import javax.jcr.version.VersionIterator;
import offset.nodes.Constants;
import offset.nodes.server.model.RepositoryStartupExtension;

/**
 *
 * @author Walter Luetgenau
 */
public class TaskSummationStartupExtension implements RepositoryStartupExtension {

    Session systemSession;
    HashSet<String> estimationProperties = new HashSet<String>();

    /**
     * Register an event listener for creation and modification stamps.
     * Called by the RepositoryExtensionsManager on startup.
     * 
     * @param systemSession
     */
    public void onStartup(Session systemSession) {
        try {
            this.systemSession = systemSession;

            // add short id observer
            systemSession.getWorkspace().getObservationManager().addEventListener(
                    new TaskObserver(Constants.TYPENAME_TASK),
                    Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED,
                    "/",
                    true,
                    null,
                    new String[] {Constants.TYPENAME_TASK},
                    true);

            estimationProperties.add(Constants.PROP_MOST_LIKELY_ESTIMATION);
            estimationProperties.add(Constants.PROP_OPTIMISTIC_ESTIMATION);
            estimationProperties.add(Constants.PROP_PESSIMISTIC_ESTIMATION);
            estimationProperties.add(Constants.PROP_WORK_DONE);

        } catch (RepositoryException ex) {
            Logger.getLogger(TaskSummationStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Stores a timestamp and a userid to the node, that has been modified. If this is a creation node, this will be done only during node creation,
     * else on every modification, depending on the registered event type.
     */
    class TaskObserver implements EventListener {

        String nodeType = null;

        /**
         * Add a shortId to the newly created node.
         * 
         * @param nodeType If not null, check for this type
         */
        public TaskObserver(String nodeType) {
            this.nodeType = nodeType;
        }

        /**
         * On an NODE_ADDED or NODE_REMOVED event, check, if this was the nodeType in question. If true, update the task nodes
         * higher in hierarchy.
         * 
         * @param events
         */
        public void onEvent(EventIterator events) {
            HashSet<String> processedPathes = new HashSet<String>();
            try {
                Node root = systemSession.getRootNode();

                while (events.hasNext()) {
                    Event event = events.nextEvent();

                    if (processedPathes.contains(event.getPath()))
                        continue;
                    String path = event.getPath().substring(1);

                    Node parent = null;

                    // calculate parent based on event type
                    if (event.getType() == Event.NODE_REMOVED) {
                        int index = path.lastIndexOf(Constants.PATH_SEPARATOR);
                        if (index < 0)
                            continue;

                        if (!root.hasNode(path.substring(0,index)))
                            continue;
                        if (path.substring(index+1).equals(Constants.CHILD_DESCRIPTION))
                            continue;
                        parent = root.getNode(path.substring(0,index));
                        if (!parent.isNodeType(this.nodeType))
                            continue;
                    } else if (event.getType() == Event.NODE_ADDED) {
                        if (!root.hasNode(path)) // renaming has been done in the meantime
                            continue;

                        Node node = root.getNode(path);
                        if (!node.isNodeType(this.nodeType))
                           continue;
                        parent = node.getParent();
                    } else if (event.getType() == Event.PROPERTY_ADDED
                            || event.getType() == Event.PROPERTY_CHANGED) {
                       int index = path.lastIndexOf(Constants.PATH_SEPARATOR);
                        if (index < 0)
                            continue;

                        String propertyName = path.substring(index + 1);
                        if (!estimationProperties.contains(propertyName))
                            continue;

                        Node node = root.getNode(path.substring(0, index));
                        parent = node.getParent();

                    }

                    while (parent.getDepth() > 0 && parent.isNodeType(Constants.TYPENAME_TASK)) {
                        updateNodeEstimationsFromChildren(parent);
                        parent = parent.getParent();
                    }

                    processedPathes.add(event.getPath());

                }

                systemSession.save();
            } catch (Throwable ex) {
                Logger.getLogger(TaskSummationStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        /**
         * Calculate and set the value of an estimation property of a task node based on the corresponding
         * values of its immediate children
         *
         * @param node
         * @throws RepositoryException
         */
        protected void updateNodeEstimationsFromChildren(Node node) throws RepositoryException {
            NodeIterator i = node.getNodes();
            
            class SummationProperty {
                public String propertyName;
                public Long value;

                public SummationProperty(String propertyName, Long value) {
                    this.propertyName = propertyName;
                    this.value = value;
                }
            }

            SummationProperty[] summationProperties = new SummationProperty[] {
                new SummationProperty(Constants.PROP_MOST_LIKELY_ESTIMATION, new Long(0)),
                new SummationProperty(Constants.PROP_OPTIMISTIC_ESTIMATION, new Long(0)),
                new SummationProperty(Constants.PROP_PESSIMISTIC_ESTIMATION, new Long(0)),
                new SummationProperty(Constants.PROP_WORK_DONE, new Long(0))
            };

            while (i.hasNext()) {
                Node child = i.nextNode();

                for (SummationProperty property : summationProperties) {
                    if (child.hasProperty(property.propertyName))
                        property.value += child.getProperty(property.propertyName).getLong();
                }
            }

            if (!node.isCheckedOut())
                node.checkout();

            for (SummationProperty property : summationProperties)
                node.setProperty(property.propertyName, property.value.longValue());
        }

     }
}
